﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Xml.Schema;
using System.Diagnostics;
using Commons.Collections;
using NVelocity;
using NVelocity.App;
using NVelocity.Context;

namespace BITestGenerator
{
    public class TestSet
    {
        public class TestDatabaseConnection
        {
            public string Type {get; set;}
            public string Value {get; set;}
        }

        public class Test
        {
            public class ExpectedResult
            {
                private string _format = string.Empty;

                public string Ordinal { get; set; }
                public string ExpectedValue { get; set; }
                public string Type { get; set; }
                public string Assertion { get; set; }
                public string Format
                {
                    get {
                        string defaultFormat = "";
                        if (Type.ToLower() == "decimal") defaultFormat = "g";
                        return _format == string.Empty ? @"""" + defaultFormat + @"""" : @"""" + _format + @"""";
                    }
                    set { _format = value.Trim(); }
                }
            }

            private List<ExpectedResult> _expectedResults = new List<ExpectedResult>();
            private string _name = string.Empty;

            public string Name
            {
                get { return _name; }
                set { _name = value.Replace(" ", string.Empty); }
            }
            public string Query { get; set; }
            public List<ExpectedResult> ExpectedResults { get { return _expectedResults; } }           
        }

        private List<Test> _tests = new List<Test>();
        private string _name = string.Empty;

        public string Name
        {
            get { return _name; }
            set { _name = value.Replace(" ", string.Empty); }
        }
        public string Type { get; set; }
        public string XmlResultElementName { get; set; }
        public TestDatabaseConnection DatabaseConnection { get; set; }
        public List<Test> Tests { get { return _tests; } }

    }

    public class Program
    {
        static bool xmlValidationErrorsFound = false;

        static void Main(string[] args)
        {
            ShowProgramInfo();

            /*
             * Get XML file to load
             */
            if (args.Length != 1)
            {
                Console.WriteLine("Please specify the XML file to load.");
                Console.WriteLine("eg: QueryUnitPOC MyTests.qut");
                Console.WriteLine("eg: QueryUnitPOC c:\\MyTests.qut");
                Console.WriteLine();
                return;
            }
            string xmlTestFile = args[0];

            /*
             * Load XML
             */
            Console.WriteLine("Loading {0}...", xmlTestFile);
            XmlSchemaSet xmlSchemaSet = new XmlSchemaSet();
            xmlSchemaSet.Add("urn:queryunit", @".\QueryUnit.xsd");
            
            XmlReaderSettings xmlReaderSettings = new XmlReaderSettings();
            xmlReaderSettings.ValidationType = ValidationType.Schema;
            xmlReaderSettings.Schemas = xmlSchemaSet;
            xmlReaderSettings.ValidationEventHandler += new ValidationEventHandler(xmlReaderSettings_ValidationEventHandler);

            XmlReader xmlReader = XmlReader.Create(xmlTestFile, xmlReaderSettings);
            XmlNameTable nameTable = xmlReader.NameTable;
            XmlNamespaceManager namespaceManager = new XmlNamespaceManager(nameTable);
            namespaceManager.AddNamespace("qu", "urn:queryunit");

            XDocument xDoc = XDocument.Load(xmlReader);
            if (xmlValidationErrorsFound) return;

            /*
             * Initialize NVelocity
             */
            Console.WriteLine("Initializing NVelocity...");
            VelocityEngine velocity = new VelocityEngine();

            ExtendedProperties props = new ExtendedProperties();
            velocity.Init(props);

            Template template = velocity.GetTemplate(@".\ClassTemplate.vt");

            /* 
             * Define Velocity Context using values coming from XML file
             */
            Console.WriteLine("Creating NVelocity Context...");
            VelocityContext context = new VelocityContext();

            var testSetElements = from e in xDoc.XPathSelectElements("/qu:QueryUnit/qu:TestSets/qu:TestSet", namespaceManager) select e;

            List<TestSet> testSets = new List<TestSet>();
            foreach (XElement testSetElement in testSetElements)
            {
                TestSet ts = new TestSet();
                ts.Name = testSetElement.Attribute("name").Value;
                ts.Type = testSetElement.Attribute("type").Value;
                Console.WriteLine("TestSet (" + ts.Type + "): " + ts.Name);

                TestSet.TestDatabaseConnection tsdc = new TestSet.TestDatabaseConnection();
                XElement dcElement = testSetElement.XPathSelectElement("qu:Configuration/qu:DatabaseConnection", namespaceManager);
                if (ts.Type == "MDX")
                {
                    tsdc.Type = "AdomdConnection";
                    ts.XmlResultElementName = "CellSetResult";
                }
                else if (ts.Type == "TSQL")
                {
                    tsdc.Type = "SqlConnection";
                    ts.XmlResultElementName = "ResultSet";
                }
                else
                {
                    throw new ApplicationException("TestSet Type can be only 'TSQL' or 'MDX'");
                }
                tsdc.Value = dcElement.Value;
                ts.DatabaseConnection = tsdc;

                foreach (XElement testElement in testSetElement.XPathSelectElements("qu:TestFixture/qu:Test", namespaceManager))
                {
                    TestSet.Test test = new TestSet.Test();
                    test.Name = testElement.Attribute("name").Value;
                    test.Query = testElement.XPathSelectElement("qu:Query", namespaceManager).Value.Replace(@"""", @"""""");
                    Console.WriteLine("\tTest: " + test.Name);

                    var expectedResultsElement = testElement.XPathSelectElements("qu:ExpectedResults/qu:" + ts.XmlResultElementName, namespaceManager);
                    foreach (XElement result in expectedResultsElement)
                    {
                        TestSet.Test.ExpectedResult expectedResult = new TestSet.Test.ExpectedResult();

                        expectedResult.Ordinal = result.Attribute("ordinal").Value;
                        expectedResult.Assertion = result.Attribute("assertion").Value;
                        expectedResult.Type = result.Attribute("type").Value;
                        expectedResult.ExpectedValue = result.Attribute("value").Value;
                        if (result.Attribute("format") != null) expectedResult.Format = result.Attribute("format").Value; 

                        test.ExpectedResults.Add(expectedResult);                        
                    }

                    ts.Tests.Add(test);
                }                

                testSets.Add(ts);
            }

            context.Put("testSets", testSets);

            Console.WriteLine("Preparing...");
            string fileName = Path.GetFileNameWithoutExtension(xmlTestFile);     
            string sourceCodeFileName = string.Format(@"./test-{0}.cs", fileName);
            string dllFileName = string.Format(@"./test-{0}.dll", fileName);
            if (File.Exists(sourceCodeFileName)) File.Delete(sourceCodeFileName);
            if (File.Exists(dllFileName)) File.Delete(dllFileName);

            Console.WriteLine("Creating code...");
            StringWriter writer = new StringWriter();
            template.Merge(context, writer);
            string code = writer.GetStringBuilder().ToString();

            Console.WriteLine("Saving...");                  
            File.WriteAllText(sourceCodeFileName, code);

            Console.WriteLine("Compiling...");
            CSharpCodeProvider codeProvider = new CSharpCodeProvider();
            CompilerParameters parameters = new CompilerParameters();
            parameters.ReferencedAssemblies.Add(@"mscorlib.dll");
            parameters.ReferencedAssemblies.Add(@"System.dll");
            parameters.ReferencedAssemblies.Add(@"System.Data.dll");
            parameters.ReferencedAssemblies.Add(QueryUnitPOC.Properties.Settings.Default.AdomdClient);
            parameters.ReferencedAssemblies.Add(QueryUnitPOC.Properties.Settings.Default.NUnitFramework);            
            parameters.GenerateExecutable = false;
            parameters.OutputAssembly = dllFileName;
            parameters.CompilerOptions = "/optimize";
            parameters.TempFiles = new TempFileCollection(".", false);

            CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, code);

            if (results.Errors.Count > 0)
            {
                results.Errors.Cast<CompilerError>().ToList().ForEach(error => Console.WriteLine("({0}): {1}", error.Line, error.ErrorText));
                Console.WriteLine("!! Compilation Failed.");
                Console.Read(); 
                return;
            }
            

            Console.WriteLine("Launching NUnit...");
            ProcessStartInfo psi = new ProcessStartInfo(QueryUnitPOC.Properties.Settings.Default.NUnitGUIRunner, dllFileName);
            Process p = Process.Start(psi);
            p.WaitForInputIdle();

            Console.WriteLine("NUnit started...");
            p.WaitForExit();
            Console.WriteLine("NUnit terminated...");

            Console.WriteLine("Done.");
            //Console.Read();            
        }

        static void xmlReaderSettings_ValidationEventHandler(object sender, ValidationEventArgs e)
        {
            Console.WriteLine("XML Validation Error: " + e.Message);
            xmlValidationErrorsFound = true;
        }

        /*
        * Show program and authors informations
        */
        private static void ShowProgramInfo()
        {
            Assembly executingAssembly = System.Reflection.Assembly.GetExecutingAssembly();
            string copyright = ((AssemblyCopyrightAttribute)executingAssembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), true)[0]).Copyright;
            string version = executingAssembly.GetName().Version.ToString();
            string title = ((AssemblyTitleAttribute)executingAssembly.GetCustomAttributes(typeof(AssemblyTitleAttribute), true)[0]).Title;

            Console.WriteLine(title + " " + copyright + " v." + version);
            Console.WriteLine("Licensed under the Microsoft Public License (Ms-PL).");
            Console.WriteLine("More info here: http://queryunit.codeplex.com");
            Console.WriteLine();
            Console.WriteLine("Alpha version, use at your own risk!");
            Console.WriteLine();
            Console.WriteLine("Target platform: " + (IntPtr.Size * 8).ToString() + "bit");
            Console.WriteLine();
        }
    }
}
